/*
 * @Author: lineCode 1145938682@qq.com
 * @Date: 2025-08-28 22:42:19
 * @LastEditors: lineCode 1145938682@qq.com
 * @LastEditTime: 2025-11-09 22:10:47
 * @FilePath: /shorthand-go/cmd/export/main.go
 */
package main

import (
	"database/sql"
	"flag"
	"fmt"
	"os"
	"path/filepath"
	"sort"
	"strings"
	"time"

	_ "github.com/go-sql-driver/mysql"
	"go.uber.org/zap"
)

// TableInfo 表信息结构体
type TableInfo struct {
	Name      string
	CreateSQL string
}

func main() {
	// 定义命令行参数
	var (
		dsn          string
		outputDir    string
		version      string
		ignoreTables string
	)

	flag.StringVar(&dsn, "dsn", "", "MySQL数据库连接字符串 (格式: user:password@tcp(host:port)/database)")
	flag.StringVar(&outputDir, "output", "./dao/migration/sql", "迁移文件输出目录")
	flag.StringVar(&version, "version", "", "迁移版本号 (默认为当前时间戳)")
	flag.StringVar(&ignoreTables, "ignore", "migrations", "忽略的表名，多个表用逗号分隔")
	flag.Parse()

	// 验证DSN参数
	if dsn == "" {
		fmt.Fprintf(os.Stderr, "错误: 必须提供数据库连接字符串\n")
		flag.Usage()
		os.Exit(1)
	}

	// 如果没有指定版本号，使用当前时间戳
	if version == "" {
		version = time.Now().Format("20060102150405")
	}

	// 解析忽略的表名
	ignoreTableMap := make(map[string]bool)
	for _, table := range strings.Split(ignoreTables, ",") {
		if strings.TrimSpace(table) != "" {
			ignoreTableMap[strings.TrimSpace(table)] = true
		}
	}

	// 确保输出目录存在
	if err := os.MkdirAll(outputDir, 0755); err != nil {
		fmt.Fprintf(os.Stderr, "创建输出目录失败: %v\n", err)
		os.Exit(1)
	}

	// 初始化日志
	logger, err := initLogger()
	if err != nil {
		fmt.Fprintf(os.Stderr, "初始化日志失败: %v\n", err)
		os.Exit(1)
	}
	defer logger.Sync()

	// 连接数据库
	db, err := sql.Open("mysql", dsn)
	if err != nil {
		logger.Fatal("连接数据库失败", zap.Error(err))
	}
	defer db.Close()

	// 测试连接
	if err = db.Ping(); err != nil {
		logger.Fatal("数据库连接测试失败", zap.Error(err))
	}

	logger.Info("数据库连接成功")

	// 获取数据库名
	dbName := extractDatabaseName(dsn)
	logger.Info("正在导出数据库表结构", zap.String("database", dbName))

	// 获取所有表名
	tables, err := getTables(db, dbName, ignoreTableMap)
	if err != nil {
		logger.Fatal("获取表列表失败", zap.Error(err))
	}

	if len(tables) == 0 {
		logger.Info("没有找到可导出的表")
		return
	}

	// 生成迁移文件
	migrationContent := generateMigrationContent(tables)
	migrationFileName := fmt.Sprintf("V%s__export_tables.sql", version)
	migrationPath := filepath.Join(outputDir, migrationFileName)

	if err := os.WriteFile(migrationPath, []byte(migrationContent), 0644); err != nil {
		logger.Fatal("写入迁移文件失败", zap.Error(err), zap.String("path", migrationPath))
	}

	// 生成回滚文件
	rollbackContent := generateRollbackContent(tables)
	rollbackFileName := fmt.Sprintf("V%s__rollback_export_tables.sql", version)
	rollbackPath := filepath.Join(outputDir, rollbackFileName)

	if err := os.WriteFile(rollbackPath, []byte(rollbackContent), 0644); err != nil {
		logger.Fatal("写入回滚文件失败", zap.Error(err), zap.String("path", rollbackPath))
	}

	logger.Info("数据库表结构导出完成",
		zap.Int("table_count", len(tables)),
		zap.String("migration_file", migrationPath),
		zap.String("rollback_file", rollbackPath),
	)

	fmt.Printf("成功导出 %d 个表的结构\n", len(tables))
	fmt.Printf("迁移文件: %s\n", migrationPath)
	fmt.Printf("回滚文件: %s\n", rollbackPath)
}

// initLogger 初始化日志
func initLogger() (*zap.Logger, error) {
	config := zap.NewProductionConfig()
	config.OutputPaths = []string{"stdout"}
	config.ErrorOutputPaths = []string{"stderr"}
	return config.Build()
}

// extractDatabaseName 从DSN中提取数据库名
func extractDatabaseName(dsn string) string {
	// 简单的DSN解析，假设格式为: user:password@tcp(host:port)/database
	parts := strings.Split(dsn, "/")
	if len(parts) > 1 {
		dbPart := parts[len(parts)-1]
		// 移除可能的参数部分
		if idx := strings.Index(dbPart, "?"); idx != -1 {
			dbPart = dbPart[:idx]
		}
		return dbPart
	}
	return ""
}

// getTables 获取数据库中的所有表名和创建SQL
func getTables(db *sql.DB, dbName string, ignoreTables map[string]bool) ([]TableInfo, error) {
	query := `SELECT table_name FROM information_schema.tables 
	         WHERE table_schema = ? AND table_type = 'BASE TABLE'`

	rows, err := db.Query(query, dbName)
	if err != nil {
		return nil, fmt.Errorf("查询表信息失败: %v", err)
	}
	defer rows.Close()

	var tables []TableInfo
	for rows.Next() {
		var tableName string
		if err := rows.Scan(&tableName); err != nil {
			return nil, fmt.Errorf("扫描表名失败: %v", err)
		}

		// 忽略指定的表
		if !ignoreTables[tableName] {
			// 使用SHOW CREATE TABLE获取创建语句
			createSQL, err := getCreateTableSQL(db, tableName)
			if err != nil {
				return nil, fmt.Errorf("获取表创建语句失败 %s: %v", tableName, err)
			}
			tables = append(tables, TableInfo{
				Name:      tableName,
				CreateSQL: createSQL,
			})
		}
	}

	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("遍历表信息失败: %v", err)
	}

	// 按表名排序
	sort.Slice(tables, func(i, j int) bool {
		return tables[i].Name < tables[j].Name
	})

	return tables, nil
}

// getCreateTableSQL 使用SHOW CREATE TABLE获取创建表的SQL
func getCreateTableSQL(db *sql.DB, tableName string) (string, error) {
	query := fmt.Sprintf("SHOW CREATE TABLE %s", tableName)
	row := db.QueryRow(query)

	var name, createSQL string
	if err := row.Scan(&name, &createSQL); err != nil {
		return "", fmt.Errorf("执行SHOW CREATE TABLE失败: %v", err)
	}

	return createSQL, nil
}

// generateMigrationContent 生成迁移文件内容
func generateMigrationContent(tables []TableInfo) string {
	var builder strings.Builder
	timestamp := time.Now().Format("2006-01-02 15:04:05")

	builder.WriteString("-- 数据库表结构导出迁移\n")
	builder.WriteString(fmt.Sprintf("-- 导出时间: %s\n\n", timestamp))

	for i, table := range tables {
		if i > 0 {
			builder.WriteString("\n")
		}
		builder.WriteString(fmt.Sprintf("-- 创建表: %s\n", table.Name))
		builder.WriteString(table.CreateSQL + ";\n")
	}

	return builder.String()
}

// generateRollbackContent 生成回滚文件内容
func generateRollbackContent(tables []TableInfo) string {
	var builder strings.Builder
	timestamp := time.Now().Format("2006-01-02 15:04:05")

	builder.WriteString("-- 数据库表结构导出回滚\n")
	builder.WriteString(fmt.Sprintf("-- 导出时间: %s\n\n", timestamp))

	// 按相反顺序删除表，以避免外键约束问题
	for i := len(tables) - 1; i >= 0; i-- {
		table := tables[i]
		builder.WriteString(fmt.Sprintf("-- 删除表: %s\n", table.Name))
		builder.WriteString(fmt.Sprintf("DROP TABLE IF EXISTS %s;\n\n", table.Name))
	}

	return builder.String()
}
